feat: figma copy paste#2268
Conversation
Importing feature-flag definitions from @agent-native/core/feature-flags dragged the barrel's server store -> settings/store -> db/client -> request-telemetry chain into the client dev graph. Vite dev does not tree-shake, so the browser evaluated it and request-telemetry's top-level new AsyncLocalStorage() threw against the externalized node:async_hooks stub, breaking app load in dev (prod tree-shakes it away). - Add client-safe @agent-native/core/feature-flags/registry entry (package export + dev source alias + optimizeDeps). - Resolve AsyncLocalStorage / EventEmitter lazily via process.getBuiltinModule (shared/optional-node-builtins.ts); no top-level value import, no-op fallback off Node. - Repoint design + clips shared config and all 15 feature-flags SKILL.md docs at /registry. - Add browser-safe import-graph regression guard spec.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Here's a visual recap of what changed: Open the full interactive recap |
There was a problem hiding this comment.
Builder reviewed your changes and found 3 potential issues 🟡
Review Details
Code Review Summary
This incremental review covers the new tokenless Figma clipboard path, which extracts and decodes the Kiwi buffer into editable HTML, tracks unresolved image fills for later hydration, and relays image-only clipboard pastes from the canvas iframe. The overall architecture is sound: the import action retains the REST path when available, local decoding is bounded, hydration uses access-scoped reads and durable storage, and the new UI provides a clear token-connect flow. I classified this as Standard risk because it changes shared import logic, server-side persistence, and browser clipboard handling.
Key Findings
- 🟡 MEDIUM — Transient REST failures can be silently converted into an empty fallback when no local buffer exists.
- 🟡 MEDIUM — Fallback image requests now omit all nodes after the first 50, causing visible layers to be lost in larger imports.
- 🟡 MEDIUM — The iframe base64-encodes all pasted images before host-side limits are applied, creating avoidable memory pressure on large pastes.
The review also found the access checks and bounded local decoder protections useful and appropriate for this feature.
🧪 Browser testing: Will run after this review (PR touches UI code)
| }; | ||
| }, | ||
| ); | ||
| const ids = nodeIds.slice(0, MAX_FIGMA_IMAGE_IDS_PER_REQUEST); |
There was a problem hiding this comment.
🟡 Fetch all fallback image nodes in batches
This now sends only nodeIds.slice(0, MAX_FIGMA_IMAGE_IDS_PER_REQUEST) to Figma, while the importer still allows up to 256 fallback references. The previous implementation batched every node ID; imports with more than 50 vector, mask, or unsupported layers now omit all fallback images after the first 50 and persist an incomplete screen. Restore batching for the full set or reject oversized imports rather than silently dropping visible layers.
Additional Info
Reported independently by two review agents.
| var imageFiles = Array.from(e.clipboardData?.items ?? []) | ||
| .filter(function (item) { | ||
| return item.kind === "file" && item.type.startsWith("image/"); | ||
| }) | ||
| .map(function (item) { return item.getAsFile(); }) | ||
| .filter(function (f): f is File { return Boolean(f); }); | ||
| if (imageFiles.length > 0) { | ||
| stopNativeInteraction(e); | ||
| var readPromises = imageFiles.map(function (file) { |
There was a problem hiding this comment.
🟡 Apply clipboard image limits before reading files
The bridge starts a FileReader for every image clipboard item and expands each into a base64 data URL before DesignCanvas applies its 20-file and 20 MB limits. A large multi-image paste can therefore allocate and post far more data than the host accepts, causing avoidable memory pressure or freezing the editor. Filter by count and File.size before readAsDataURL, and bound the reads.
Additional Info
Reported by one review agent.
7ddf193 to
bb07b13
Compare
There was a problem hiding this comment.
Builder reviewed your changes and found 2 potential issues 🔴
Review Details
Code Review Summary
This incremental review rechecked PR #2268 at the latest head. The two currently open comments about truncated fallback-node requests and late iframe image limits remain unresolved and were intentionally not reposted. The PR’s overall approach—local Kiwi decoding for tokenless pastes, placeholder tracking, scoped hydration, and durable image storage—is reasonable, with appropriate decoder and access guards.
New Findings
- 🔴 HIGH — Deferred image hydration calls the Figma image-fill endpoint without passing the requested image references, so the hydration action cannot reliably retrieve the URLs needed to replace placeholders.
- 🟡 MEDIUM — Hydration accepts an unbounded number of image refs while the durable mirroring path rejects more than 256, causing large valid imports to fail hydration entirely.
This remains Standard risk because the changes span shared server import logic, external API calls, persistence, and user-facing editor state.
🧪 Browser testing: Will run after this review (PR touches UI code)
| * Used by `hydrate-figma-paste-images` to fill in `about:blank` placeholders | ||
| * that the local-kiwi clipboard decode path leaves behind. | ||
| */ | ||
| export async function resolveImageFillRefs( |
There was a problem hiding this comment.
🔴 Pass image references to the Figma image-fill request
fetchImageFillUrls receives imageRefs but calls figmaGet(/files/${fileKey}/images) without sending them as query parameters. The response is then indexed by each requested ref, so hydration will return no matching URLs (or an incomplete/unbounded response) and leave the imported about:blank placeholders unresolved. Pass the supported image IDs/hashes parameter to this request, or use the endpoint variant that accepts the refs.
Additional Info
Reported independently by two review agents; anchored on the new helper declaration because the request line is outside a commentable added hunk.
| let resolvedUrls: Map<string, string>; | ||
| try { | ||
| resolvedUrls = await resolveImageFillRefs(figmaFileKey, hashesToResolve); |
There was a problem hiding this comment.
🟡 Bound or batch image references during hydration
collectImageRefHashes can return every unique image fill from a local Kiwi import, but this passes the full list to resolveImageFillRefs; its durable mirroring path rejects more than 256 URLs instead of batching. A valid paste with 257+ distinct image fills therefore hydrates none of them after the user connects Figma. Batch within the existing limit or return partial progress with a retryable remainder.
Additional Info
Reported independently by three review agents; this issue was not among the currently open comments.

Import paths
Layout & structure
Fills, strokes, effects
Vectors / icons
Text
Images (no-token paste)